//defines:
#define ScalarFieldType float

//members:
static const int maxParticles;
static NxVec3* fluidParticlePositionBuffer;
static NxF32* fluidParticleDensityBuffer;
static NxU32* numParticles;
static NxFluid* fluid;
static CIsoSurface<ScalarFieldType> s;
	
const int PhysicsLogic::maxParticles = 32000;
NxVec3* PhysicsLogic::fluidParticlePositionBuffer = NULL;
NxF32* PhysicsLogic::fluidParticleDensityBuffer = NULL;
NxU32* PhysicsLogic::numParticles = NULL;
NxFluid* PhysicsLogic::fluid = NULL;
CIsoSurface<ScalarFieldType> PhysicsLogic::s;

	
//creation:
{
	fluidParticlePositionBuffer = new NxVec3[maxParticles];
	fluidParticleDensityBuffer = new NxF32[maxParticles];
	numParticles = new NxU32(0);

	//Set structure to pass particles, and receive them after every simulation step
	NxParticleData particleData;
	particleData.numParticlesPtr = numParticles;
	particleData.bufferPos = &fluidParticlePositionBuffer[0].x;
	particleData.bufferPosByteStride = sizeof(NxVec3);
	particleData.bufferDensity = &fluidParticleDensityBuffer[0];
	particleData.bufferDensityByteStride = sizeof(NxF32);

	//Create a fluid descriptor
	NxFluidDesc fluidDesc;
	fluidDesc.kernelRadiusMultiplier = 2.3f;
	fluidDesc.restParticlesPerMeter = 10.0f;
	fluidDesc.stiffness = 150.0f;
	fluidDesc.viscosity = 22.0f*3;
	fluidDesc.restDensity = 1000.0f;
	fluidDesc.damping = 0.0f;
	fluidDesc.simulationMethod = NX_F_SPH;
	fluidDesc.initialParticleData = particleData;
	fluidDesc.particlesWriteData = particleData;
	//fluidDesc.simulationMethod = NX_F_SPH;
	if(IsHardwareAccelerationSupported() == false) {
		fluidDesc.flags &= ~NX_FF_HARDWARE;
	}

	fluid = scene->createFluid(fluidDesc);
}

//spawn particles:
} else if(key == '3') {
	if(Game::IsInDebugMode()) {
		for(int i=0; i<30; ++i) {
			if(*numParticles < maxParticles) {
				NxVec3 buf[1];
				buf[0] = NxVec3(0.01f*(float)(rand()%100), 4.0f + 0.01*(float)(rand()%20), 0.01f*(float)(rand()%100));
				NxU32 num(1);

				NxParticleData particleData;
				particleData.numParticlesPtr = &num;
				particleData.bufferPos = &buf[0].x;
				particleData.bufferPosByteStride = sizeof(NxVec3);

				fluid->addParticles(particleData);
			}
		}
	}
}

//fluid particle rendering:
if(true) {
	glDepthMask(GL_FALSE);
	glDisable(GL_LIGHTING);
	for (NxU32 p=0; p<*numParticles; p++) {
		glPushMatrix();
		NxVec3& particlePos = fluidParticlePositionBuffer[p];
		NxF32& particleDensity = fluidParticleDensityBuffer[p];
		
		glTranslatef(particlePos.x,particlePos.y,particlePos.z);
		SurfaceRenderingData fluidMaterial;
		fluidMaterial.SetColor(0.25f, 0.75f, 1.0f, 0.0005f*particleDensity);
		//fluidMaterial.SetColor(0.0005f*particleDensity, 0.0f, 1.0, 0.4f);
		Renderer::SetMaterial(fluidMaterial);

		glEnable(GL_POINT_SMOOTH);
		Renderer::DrawPoint(100.0f/particlePos.distance(camera.GetPosition()));

		glPopMatrix();
	}
	glEnable(GL_LIGHTING);
	glDepthMask(GL_TRUE);
}
	
//fluid surface rendering:
const bool fluidSurfaceRendering = false;
const bool testTwoBalls = false;
if(fluidSurfaceRendering) {
	unsigned int cf = 32;
	unsigned int cx = cf;
	unsigned int cy = 8;
	unsigned int cz = cf;

	float quality = 10.0f;
	float sf(1.0/quality);
	float sx(sf);
	float sy(sf);
	float sz(sf);

	ScalarFieldType isoLevel = 1.0f;

	const unsigned int n = cx * cy * cz;
	static DynamicArray3<ScalarFieldType> scalarField(cx, cy, cz);
	const NxVec3 ball1(cx/2+debugValue*0.2f, cy/2-2, cz/2);
	const NxVec3 ball2(cx/2, cy/2-2, cz/2);
	const NxVec3 offset(sf*cf/2, 0.0f, sf*cf/2);
	const float particleRadius = 0.3f;
	for(unsigned int z=0; z<cz; ++z) {
		for(unsigned int y=0; y<cy; ++y) {
			for(unsigned int x=0; x<cx; ++x) {
				if(testTwoBalls) {
					float rr1 = (x-ball1.x)*(x-ball1.x) + (y-ball1.y)*(y-ball1.y) + (z-ball1.z)*(z-ball1.z);
					float rr2 = (x-ball2.x)*(x-ball2.x) + (y-ball2.y)*(y-ball2.y) + (z-ball2.z)*(z-ball2.z);
					float e1 = 5/rr1;
					float e2 = 5/rr2;
					scalarField(x,y,z) = e1 + e2;
				} else {
					scalarField(x,y,z) = 0.0f;
					for (NxU32 p=0; p<*numParticles; ++p) {
						const NxVec3 pos = fluidParticlePositionBuffer[p]*quality + offset;
						const float ax = x-pos.x;
						const float ay = y-pos.y;
						const float az = z-pos.z;
						scalarField(x,y,z) += particleRadius/(ax*ax + ay*ay + az*az);
					}
				}
			}
		}
	}
	s.GenerateSurface(scalarField.GetData(), isoLevel, cx-1, cy-1, cz-1, sx, sy, sz);

	SurfaceRenderingData srd;
	srd.SetColor(1,0,0,1);
	srd.SetSpecularMaterial(1,1,1,1);
	Renderer::SetMaterial(srd);
	glPushMatrix();
	glTranslatef(offset.x,offset.y,offset.z);
	glBegin(GL_TRIANGLES);
	const unsigned int trianglesCount = s.m_nTriangles;
	for(unsigned int t=0; t<trianglesCount; ++t) {
		for(unsigned int i=0; i<3; ++i) {
			unsigned int pointID = s.m_piTriangleIndices[t*3+i];
			POINT3D& point = s.m_ppt3dVertices[pointID];
			VECTOR3D& normal = s.m_pvec3dNormals[pointID];
							
			glNormal3f(normal[2], normal[1], normal[0]);
			glVertex3f(point[2], point[1], point[0]);
		}
	}
	glEnd();
	glPopMatrix();

	s.DeleteSurface();
}
